<?php

/**
 * 短信验证码类
 */
namespace app\common\service;
use app\common\library\CommonFun;
use think\Request;

class Sms
{

    /**
     * 验证码有效时长
     * @var int 
     */
    protected static $expire = 600;

    /**
     * 最大允许检测的次数
     * @var int 
     */
    protected static $maxCheckNums = 10;

    /**
     * 一天最多能发送的次数
     * @var int
     */
    protected static $maxSendNumsDay = 100;

    /**
     * 一小时最大发送次数
     * @var int
     */
    protected static $maxSendNumsHours = 10;

    /**
     * 短信倒计时时间
     * @var int
     */
    protected static $fastSendTime = 60;


    /**
     * 发送验证码
     *
     * @param   int       $mobile   手机号
     * @param   int       $code     验证码,为空时将自动生成4位数字
     * @param   string    $event    事件
     * @return  boolean
     * @throws \Exception
     */
    public static function send($mobile, $code = NULL, $event = 'default')
    {
        $codeKey = $mobile.$event;
        $dayNumsKey = $codeKey.date('Y-m-d');
        $hoursNumsKey = $dayNumsKey.'hours';
        $timeKey = $codeKey.'send_time';

        // memcache 缓存对象
        $memobj = CommonFun::get_memcache( 'default');

        $Request = Request::instance();
        $ip = $Request->ip();
        $sms_ip_nums_key = 'str_sms_ip_num_'.$ip;
        $ipNum = (int) $memobj->get( $sms_ip_nums_key );

        if ( $ipNum > 50 ){
            throw new \Exception('今日获取验证码次数已用完！',1002 );
        } else {
            $ipNum ++;
            $memobj->set($sms_ip_nums_key, $ipNum, 60*60);
        }


        /*验证规则
         * 一个手机号，一天最多发10次
         * 一个小时最多发3次
         * 一分钟内只能请求一次
         */
        $dayNum = (int) $memobj->get( $dayNumsKey );
        if ( $dayNum > self::$maxSendNumsDay ) {
            throw new \Exception('今日获取验证码次数已用完！',1002 );
        }

        $hoursNum = (int) $memobj->get( $hoursNumsKey );

        if ( $hoursNum > self::$maxSendNumsHours) {
            throw new \Exception('一小时获取验证码次数已用完！', 1002);
        }

        $lastSendTime = (int) $memobj->get( $timeKey );
        $now = time();
        if ($now - $lastSendTime < self::$fastSendTime) {
            throw new \Exception('1分钟内你已经获取过验证码，请稍后再试。', 1003 );
        }

        //验证通过，发送验证码
        $code = is_null($code) ? mt_rand(100000, 999999) : $code;

        $param = [
            'mobile' => $mobile,
            'content' => $code,
            'event'   => $event
        ];

        // 检测若为批量用户 则不封禁用户
        if ( CommonFun::skip_validation( $mobile ) ) {
            return true;
        }

        // 发送短信
        $result = SmsService::sendCode( $param );

        if ( !$result )
        {
            throw new \Exception('网络错误，请稍后重试');
        }

        //发送成功，redis，用于后续验证，后续可以封装优化 todo
        $memobj->set( $codeKey, $code, self::$expire );   //记录验证码，设置有效期10分钟
        $memobj->set($timeKey, $now, 60*60*24); //更新发送时间

        $dayNum ++;
        $hoursNum ++;

        $memobj->set($dayNumsKey, $dayNum, 60*60*24);   //记录一天发送次数
        $memobj->set($hoursNumsKey, $hoursNum, 60*60);    //记录一小时发送数

        return TRUE;
    }

    /**
     * 校验验证码
     *
     * @param int $mobile 手机号
     * @param   int       $code       验证码
     * @param   string    $event      事件
     * @return  boolean
     * @throws \Exception
     */
    public static function check($mobile, $code, $event = 'default')
    {
        // memcache 缓存对象
        $memobj = CommonFun::get_memcache( 'default');

        $codeKey = $mobile.$event;
        $checkNumsKey = $codeKey.'check_nums';

        $smsCode = $memobj->get( $codeKey );
        if ( !$smsCode ) {
            throw new \Exception('验证码已失效！' ,1006 );
        }

        $checkNums = $memobj->get( $checkNumsKey );

        if ( $checkNums && $checkNums > self::$maxCheckNums ) {
            throw new \Exception('超出验证次数' );
        }

        $checkNums++;

        if ( $smsCode != $code ) {
            $memobj->set( $checkNumsKey, $checkNums, self::$expire ); //更新发送时间
            throw new \Exception( '验证码错误' ,1003 );
        }

        //验证成功
        $memobj->rm( $checkNumsKey );
        $memobj->rm( $codeKey );

        return true;

    }


}
